/* -*- csharp -*- */
/*----------------------------------------------------------------------
Compiler Generator CocoXml/R,
Copyright (c) 2008 Charles Wang <charlesw123456@gmail.com>

This program is free software; you can redistribute it and/or modify it 
under the terms of the GNU General Public License as published by the 
Free Software Foundation; either version 2, or (at your option) any 
later version.

This program is distributed in the hope that it will be useful, but 
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
for more details.

You should have received a copy of the GNU General Public License along 
with this program; if not, write to the Free Software Foundation, Inc., 
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

As an exception, it is allowed to write an extension of CocoXml/R that is
used as a plugin in non-free software.

If not otherwise stated, any source code generated by CocoXml/R (other than 
CocoXml/R itself) does not fall under the GNU General Public License.
-----------------------------------------------------------------------*/
/*---- Begin ----*/
using System;
using System.IO;
using System.Collections.Generic;
using System.Xml;

/*---- Namespace ----*/

public class Token {
/*---- Options ----*/
    public int kind;
    public int col;
    public int line;
    public string val;
}

public class TagInfo {
    public int startToken;
    public int endToken;
}

public class XmlLangDefinition {
    public bool[]                      useVector;
    public Dictionary<string, TagInfo> Tags;
    public Dictionary<string, int>     Attrs;
    public Dictionary<string, int>     PInstructions;

    public XmlLangDefinition() {
	useVector = new bool[Token.numOptions];
	Tags = new Dictionary<string, TagInfo>();
	Attrs = new Dictionary<string, int>();
	PInstructions = new Dictionary<string, int>();
    }

    public void AddTag(string Name, int StartToken, int EndToken) {
	TagInfo tinfo = new TagInfo();
	tinfo.startToken = StartToken;
	tinfo.endToken = EndToken;
	Tags.Add(Name, tinfo);
    }

    public void AddAttr(string Name, int AttrToken) {
	Attrs.Add(Name, AttrToken);
    }

    public void AddProcessingInstruction(string PIName, int PIToken) {
	PInstructions.Add(PIName, PIToken);
    }
}

public class tagStackEle {
    public bool unknownNamespace;
    public bool unknownTag;
    public XmlLangDefinition xldef;
}

public class XmlScanner {
    enum XSStatus { XSCommon, XSFirstAttr, XSNextAttr };

    const char EOL = '\n';
    const int eofSym = 0;
/*---- Declarations ----*/

    Stream stream;
    XmlTextReader                         xmlreader;
    Dictionary<string, XmlLangDefinition> XmlLangMap;
    Stack<tagStackEle>                    tagStack;
    XSStatus                              status;
    bool                                  inEmptyElement;
    string                                emptyNSURI;
    string                                emptyLName;
    List<Token>                           tokens;
    int                                   peek;

    public XmlScanner (string fileName) {
	try {
	    stream = new FileStream(fileName, FileMode.Open,
				    FileAccess.Read, FileShare.Read);
	    Init();
	} catch (IOException) {
	    throw new FatalError("Can not open file " + fileName);
	}
    }

    public XmlScanner (Stream s) {
	stream = s;
	Init();
    }

    void Init() {
	XmlLangDefinition curXLDef;

	xmlreader = new XmlTextReader(stream);
	XmlLangMap = new Dictionary<string, XmlLangDefinition>();
	tagStack = new Stack<tagStackEle>();
	status = XSStatus.XSCommon;
	inEmptyElement = false;
	tokens = new List<Token>();
	peek = 0;

/*---- Initialization ----*/

	tagStackEle  tsEle = new tagStackEle();
	tsEle.unknownNamespace = false;
	tsEle.unknownTag = false;
	tsEle.xldef = XmlLangMap[""];
	tagStack.Push(tsEle);
    }

    int StartElement2Kind(string NamespaceURI, string TagName) {
	tagStackEle curEle = tagStack.Peek();
	tagStackEle newEle = new tagStackEle();

	if (!XmlLangMap.ContainsKey(NamespaceURI)) {
	    newEle.unknownNamespace = true;
	    newEle.unknownTag = false;
	    newEle.xldef = curEle.xldef;
	    tagStack.Push(newEle);
	    return useKindVector[(int)Token.Options.UNKNOWN_NAMESPACE];
	}
	newEle.unknownNamespace = false;
	newEle.xldef = XmlLangMap[NamespaceURI];
	if (!newEle.xldef.Tags.ContainsKey(TagName)) {
	    newEle.unknownTag = true;
	    tagStack.Push(newEle);
	    return useKindVector[(int)Token.Options.UNKNOWN_TAG];
	}
	newEle.unknownTag = false;
	tagStack.Push(newEle);
	return newEle.xldef.Tags[TagName].startToken;
    }

    int EndElement2Kind(string NamespaceURI, string TagName) {
	tagStackEle curEle = tagStack.Pop();

	if (curEle.unknownNamespace) {
	    return useKindVector[(int)Token.Options.END_UNKNOWN_NAMESPACE];
	}
	if (curEle.unknownTag)
	    return useKindVector[(int)Token.Options.END_UNKNOWN_TAG];
	return curEle.xldef.Tags[TagName].endToken;
    }

    int Attribute2Kind(string NamespaceURI, string AttrName) {
	XmlLangDefinition xldef;
	tagStackEle curEle = tagStack.Peek();

	// assert(!curEle.unknownNamespace and !curEle.unknownTag);
	if (NamespaceURI == "") xldef = curEle.xldef;
	else if (!XmlLangMap.ContainsKey(NamespaceURI))
	    return useKindVector[(int)Token.Options.UNKNOWN_ATTR_NAMESPACE];
	else xldef = XmlLangMap[NamespaceURI];
	if (!xldef.Attrs.ContainsKey(AttrName))
	    return useKindVector[(int)Token.Options.UNKNOWN_ATTR];
	return xldef.Attrs[AttrName];
    }

    int ProcessingInstruction2Kind(string PIName) {
	tagStackEle curEle = tagStack.Peek();
	// Should I always use 'XmlLangMap[""]' but not 'curEle.xldef' here?
	if (!curEle.xldef.PInstructions.ContainsKey(PIName))
	    return useKindVector[(int)Token.Options.UNKNOWN_PROCESSING_INSTRUCTION];
	return curEle.xldef.PInstructions[PIName];
    }

    int Others2Kind(XmlNodeType ntype) {
	Token.Options opt;
	tagStackEle curEle = tagStack.Peek();

	switch (ntype) {
	case XmlNodeType.Text:
	    if (curEle.unknownNamespace) opt = Token.Options.UNS_TEXT;
	    else if (curEle.unknownTag) opt = Token.Options.UT_TEXT;
	    else opt = Token.Options.TEXT;
	    break;
	case XmlNodeType.CDATA:
	    if (curEle.unknownNamespace) opt = Token.Options.UNS_CDATA;
	    else if (curEle.unknownTag) opt = Token.Options.UT_CDATA;
	    else opt = Token.Options.CDATA;
	    break;
	case XmlNodeType.Whitespace:
	    if (curEle.unknownNamespace) opt = Token.Options.UNS_WHITESPACE;
	    else if (curEle.unknownTag) opt = Token.Options.UT_WHITESPACE;
	    else opt = Token.Options.WHITESPACE;
	    break;
	case XmlNodeType.Comment:
	    if (curEle.unknownNamespace) opt = Token.Options.UNS_COMMENT;
	    else if (curEle.unknownTag) opt = Token.Options.UT_COMMENT;
	    else opt = Token.Options.COMMENT;
	    break;
	default:
	    return -1;
	}
	return curEle.xldef.useVector[(int)opt] ? useKindVector[(int)opt] : -1;
    }

    void AppendToken() {
	int kind = -1;
	Token new_token = null;
	while (kind < 0) {
	    switch (status) {
	    case XSStatus.XSCommon:
		if (inEmptyElement) {
		    kind = EndElement2Kind(emptyNSURI, emptyLName);
		    inEmptyElement = false;
		    break;
		} else if (!xmlreader.Read()) { kind = eofSym; break; }
		switch (xmlreader.NodeType) {
		case XmlNodeType.Element:
		    kind = StartElement2Kind(xmlreader.NamespaceURI,
					     xmlreader.LocalName);
		    if (xmlreader.IsEmptyElement) {
			emptyNSURI = xmlreader.NamespaceURI;
			emptyLName = xmlreader.LocalName;
			inEmptyElement = true;
		    }
		    if (kind >= 0) status = XSStatus.XSFirstAttr;
		    break;
		case XmlNodeType.EndElement:
		    kind = EndElement2Kind(xmlreader.NamespaceURI,
					   xmlreader.Name);
		    break;
		case XmlNodeType.ProcessingInstruction:
		    kind = ProcessingInstruction2Kind(xmlreader.Name);
		    break;
		default:
		    kind = Others2Kind(xmlreader.NodeType);
		    break;
		}
		break;
	    case XSStatus.XSFirstAttr:
		if (!xmlreader.MoveToFirstAttribute()) {
		    status = XSStatus.XSCommon;
		    kind = -1;
		} else {
		    status = XSStatus.XSNextAttr;
		    kind = Attribute2Kind(xmlreader.NamespaceURI,
					  xmlreader.LocalName);
		}
		break;
	    case XSStatus.XSNextAttr:
		if (!xmlreader.MoveToNextAttribute()) {
		    status = XSStatus.XSCommon;
		    kind = -1;
		} else {
		    kind = Attribute2Kind(xmlreader.NamespaceURI,
					  xmlreader.LocalName);
		}
		break;
	    }
	}
	new_token = new Token();
	new_token.kind = kind;
	new_token.line = xmlreader.LineNumber;
	new_token.col = xmlreader.LinePosition;
	new_token.val = xmlreader.Value;
	tokens.Add(new_token);
    }

    public Token Scan () {
	Token token;
	if (tokens.Count == 0) AppendToken();
	token = tokens[0];
	tokens.RemoveAt(0);
	return token;
    }

    public Token Peek () {
	if (peek == tokens.Count) AppendToken();
	return tokens[peek ++];
    }

    public void ResetPeek () { peek = 0; }
}

/*---- $$$ ----*/
